#include "Printer.hh"
#include "PNG.hh"
#include "FileOperations.hh"
#include "IntegerSetting.hh"
#include "MSXMotherBoard.hh"
#include "CliComm.hh"
#include "MSXException.hh"
#include "Math.hh"
#include "MemBuffer.hh"
#include "serialize.hh"
#include "vla.hh"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
#include <memory>
#include <vector>

using std::max;
using std::min;
using std::string;

namespace openmsx {

class Paper
{
public:
	Paper(unsigned x, unsigned y, double dotSizeX, double dotSizeY);

	std::string save() const;
	void setDotSize(double sizeX, double sizeY);
	void plot(double x, double y);

private:
	byte& dot(unsigned x, unsigned y);

	MemBuffer<byte> buf;
	std::vector<int> table;

	double radiusX;
	double radiusY;
	int radius16;

	unsigned sizeX;
	unsigned sizeY;
};


bool PrinterCore::getStatus(EmuTime::param /*time*/)
{
	return false; // false = low = ready
}

void PrinterCore::setStrobe(bool strobe, EmuTime::param /*time*/)
{
	if (!strobe && prevStrobe) {
		// falling edge
		write(toPrint);
	}
	prevStrobe = strobe;
}

void PrinterCore::writeData(byte data, EmuTime::param /*time*/)
{
	toPrint = data;
}

void PrinterCore::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
{
	// nothing
}

void PrinterCore::unplugHelper(EmuTime::param /*time*/)
{
	forceFormFeed();
}


/*
// class RawPrinter

RawPrinter::RawPrinter()
{
	Properties* properties = propGetGlobalProperties();
	hFile = CreateFile(properties->ports.Lpt.portName, GENERIC_WRITE,
	                   0, nullptr, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, nullptr);
	if (hFile == INVALID_HANDLE_VALUE) {
		throw MSXException();
	}
}

void RawPrinter::~RawPrinter()
{
	CloseHandle(hFile);
}

void RawPrinter::write(byte value)
{
	unsigned dwWritten;
	WriteFile(hFile, &value, 1, &dwWritten, nullptr);
}

void RawPrinter::forceFormFeed()
{
}
*/


// class ImagePrinter

ImagePrinter::ImagePrinter(MSXMotherBoard& motherBoard_, bool graphicsHiLo_)
	: motherBoard(motherBoard_)
	, graphicsHiLo(graphicsHiLo_)
{
	dpiSetting = motherBoard.getSharedStuff<IntegerSetting>(
		"print-resolution",
		motherBoard.getCommandController(), "print-resolution",
		"resolution of the output image of emulated dot matrix printer in DPI",
		300, 72, 1200);

	letterQuality = false;
	bold = false;
	proportional = false;
	italic = false;
	superscript = false;
	subscript = false;
	doubleWidth = false;
	underline = false;
	doubleStrike = false;
	escSequence = false;
	alternateChar = false;
	detectPaperOut = false;
	japanese = false;
	normalAfterLine = false;
	ninePinGraphics = false;
	leftToRight = false;
	elite = false;
	compressed = false;
	noHighEscapeCodes = false;
	eightBit = 0;
	perforationSkip = 0;
	remainingCommandBytes = 0;
	sizeEscPos = 0;
	sizeRemainingDataBytes = 0;
	ramLoadOffset = 0;
	ramLoadEnd = 0;
	countryCode = CC_USA;

	printAreaTop = -1.0;
	printAreaBottom = 0.0;
}

ImagePrinter::~ImagePrinter()
{
	flushEmulatedPrinter();
}

void ImagePrinter::write(byte data)
{
	if (ramLoadOffset < ramLoadEnd) {
		fontInfo.ram[ramLoadOffset++] = data;
	} else if (sizeRemainingDataBytes) {
		if (eightBit == 0) {
			data &= 0x80;
		} else if (eightBit == 1) {
			data |= 0x80;
		}
		printGraphicByte(data);
		sizeRemainingDataBytes--;
	} else if (escSequence) {
		escSequence = false;

		memset(&(abEscSeq), 0, sizeof(abEscSeq));
		*(abEscSeq) = data;
		sizeEscPos = 1;

		remainingCommandBytes = calcEscSequenceLength(data);

		if (!remainingCommandBytes) {
			processEscSequence();
		}
	} else if (remainingCommandBytes) {
		abEscSeq[sizeEscPos++] = data;

		if (!--remainingCommandBytes) {
			processEscSequence();
		}
	} else {
		processCharacter(data);
	}
}

void ImagePrinter::forceFormFeed()
{
	flushEmulatedPrinter();
}

void ImagePrinter::resetEmulatedPrinter()
{
	resetSettings();

	pageHeight = pageTop + lines * lineFeed;
	hpos       = leftBorder;
	vpos       = pageTop;
}

void ImagePrinter::plot9Dots(double x, double y, unsigned pattern)
{
	for (int i = 0; i < 9; ++i) {
		if (pattern & (1 << i)) {
			paper->plot(x, y + (8 - i) * pixelSizeY);
		}
	}
}

void ImagePrinter::printGraphicByte(byte data)
{
	ensurePrintPage();

	double destY      = vpos * pixelSizeY;
	double destHeight = pixelSizeY * 9.0;

	// Print Data to high 8 bits
	unsigned charBits = (graphicsHiLo ? Math::reverseByte(data) : data) << 1;

	printAreaTop    = min(printAreaTop, destY);
	printAreaBottom = max(printAreaBottom, destY + destHeight);

	// Print bit-mask
	plot9Dots(hpos * pixelSizeX, destY, charBits);

	// Move print-position...
	seekPrinterHeadRelative(1.0 / graphDensity);
}

void ImagePrinter::seekPrinterHeadRelative(double offset)
{
	hpos += offset;
	if (unsigned(hpos) > rightBorder) {
		hpos = leftBorder;
		vpos += lineFeed;
		if (vpos >= pageHeight) {
			doubleWidth ^= normalAfterLine;
			flushEmulatedPrinter();
		}
	}
}

void ImagePrinter::ensurePrintPage()
{
	if (!paper) {
		// A4 paper format (210mm x 297mm) at 300dpi
		// TODO make this configurable
		int dpi = dpiSetting->getInt();
		auto paperSizeX = unsigned((210 / 25.4) * dpi);
		auto paperSizeY = unsigned((297 / 25.4) * dpi);

		unsigned dotsX, dotsY;
		getNumberOfDots(dotsX, dotsY);
		pixelSizeX = double(paperSizeX) / dotsX;
		pixelSizeY = double(paperSizeY) / dotsY;

		paper = std::make_unique<Paper>(paperSizeX, paperSizeY,
		                                pixelSizeX, pixelSizeY);
	}
}

void ImagePrinter::flushEmulatedPrinter()
{
	if (paper) {
		if (printAreaBottom > printAreaTop) {
			try {
				string filename = paper->save();
				motherBoard.getMSXCliComm().printInfo(
					"Printed to ", filename);
			} catch (MSXException& e) {
				motherBoard.getMSXCliComm().printWarning(
					"Failed to print: ", e.getMessage());
			}
			printAreaTop = -1.0;
			printAreaBottom = 0.0;
		}
		paper.reset();
	}
	hpos = leftBorder;
	vpos = pageTop;
}

static unsigned compress9(unsigned a)
{
	unsigned result = 0;
	for (unsigned i = 0; i < 9; ++i) {
		if (a & (1 << i)) {
			result |= 1 << (i / 2);
		}
	}
	return result;
}
void ImagePrinter::printVisibleCharacter(byte data)
{
	ensurePrintPage();

	double iYPos = 0;
	byte* charBitmap = (fontInfo.useRam ? fontInfo.ram : fontInfo.rom)
	                 + fontInfo.charWidth * data;
	byte attribute = charBitmap[0];
	unsigned start = (attribute >> 4) & 0x07;
	unsigned end = attribute & 0x0f;
	unsigned topBits = attribute >> 7;
	bool script = superscript || subscript;

	if (!proportional) {
		start = 0;  // Fixed width font
		end   = fontInfo.charWidth - 1;
	}
	if (subscript) {
		iYPos /= 2.0;
		iYPos += pixelSizeY * 4.5;
	}
	if (script) {
		iYPos -= pixelSizeY * 4.5;
	}

	double hPos = hpos;
	double headRelative = (doubleWidth ? 2 : 1) * fontInfo.pixelDelta / fontDensity;

	double destY = vpos * pixelSizeY + iYPos;
	double destHeight = pixelSizeY * 9.0;
	double dblStrikeOffset = doubleStrike ? (pixelSizeY / 2.5) : 0.0;
	printAreaTop    = min(printAreaTop, destY);
	printAreaBottom = max(printAreaBottom, destY + destHeight + dblStrikeOffset);

	for (unsigned i = start; i < end; ++i) {
		unsigned charBits = unsigned(charBitmap[i + 1]) << topBits;

		if (underline) {
			charBits |= 2;
		}
		if (script) {
			charBits = compress9(charBits);
		}

		for (int d = 0; d <= (doubleWidth?1:0); d++) {
			for (int b = 0; b <= (bold?1:0); ++b) {
				for (int y = 0; y <= (doubleStrike?1:0); ++y) {
					double destX = (hPos + (d + b / 2.0) / fontDensity) * pixelSizeX;
					plot9Dots(destX, destY + y * dblStrikeOffset, charBits);
				}
			}
		}
		hPos += headRelative;
	}
	seekPrinterHeadRelative((1 + end - start) * headRelative);
}


// class ImagePrinterMSX

// MSX-Font taken from NMS8250 BIOS ROM
constexpr byte MSXFontRaw[256 * 8] = {
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 0
	0x3C, 0x42, 0xA5, 0x81, 0xA5, 0x99, 0x42, 0x3C,  // 1
	0x3C, 0x7E, 0xDB, 0xFF, 0xFF, 0xDB, 0x66, 0x3C,  // 2
	0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00,  // 3
	0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00,  // 4
	0x10, 0x38, 0x54, 0xFE, 0x54, 0x10, 0x38, 0x00,  // 5
	0x10, 0x38, 0x7C, 0xFE, 0xFE, 0x10, 0x38, 0x00,  // 6
	0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00,  // 7
	0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xFF,  // 8
	0x38, 0x44, 0x82, 0x82, 0x82, 0x44, 0x38, 0x00,  // 9
	0xC7, 0xBB, 0x7D, 0x7D, 0x7D, 0xBB, 0xC7, 0xFF,  // 10
	0x0F, 0x03, 0x05, 0x79, 0x88, 0x88, 0x88, 0x70,  // 11
	0x38, 0x44, 0x44, 0x44, 0x38, 0x10, 0x7C, 0x10,  // 12
	0x30, 0x28, 0x24, 0x24, 0x28, 0x20, 0xE0, 0xC0,  // 13
	0x3C, 0x24, 0x3C, 0x24, 0x24, 0xE4, 0xDC, 0x18,  // 14
	0x10, 0x54, 0x38, 0xEE, 0x38, 0x54, 0x10, 0x00,  // 15
	0x10, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x10, 0x10,  // 16
	0x10, 0x10, 0x10, 0xFF, 0x00, 0x00, 0x00, 0x00,  // 17
	0x00, 0x00, 0x00, 0xFF, 0x10, 0x10, 0x10, 0x10,  // 18
	0x10, 0x10, 0x10, 0xF0, 0x10, 0x10, 0x10, 0x10,  // 19
	0x10, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10, 0x10,  // 20
	0x10, 0x10, 0x10, 0xFF, 0x10, 0x10, 0x10, 0x10,  // 21
	0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,  // 22
	0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,  // 23
	0x00, 0x00, 0x00, 0x1F, 0x10, 0x10, 0x10, 0x10,  // 24
	0x00, 0x00, 0x00, 0xF0, 0x10, 0x10, 0x10, 0x10,  // 25
	0x10, 0x10, 0x10, 0x1F, 0x00, 0x00, 0x00, 0x00,  // 26
	0x10, 0x10, 0x10, 0xF0, 0x00, 0x00, 0x00, 0x00,  // 27
	0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81,  // 28
	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,  // 29
	0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01,  // 30
	0x00, 0x10, 0x10, 0xFF, 0x10, 0x10, 0x00, 0x00,  // 31
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 32
	0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x20, 0x00,  // 33
	0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,  // 34
	0x50, 0x50, 0xF8, 0x50, 0xF8, 0x50, 0x50, 0x00,  // 35
	0x20, 0x78, 0xA0, 0x70, 0x28, 0xF0, 0x20, 0x00,  // 36
	0xC0, 0xC8, 0x10, 0x20, 0x40, 0x98, 0x18, 0x00,  // 37
	0x40, 0xA0, 0x40, 0xA8, 0x90, 0x98, 0x60, 0x00,  // 38
	0x10, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,  // 39
	0x10, 0x20, 0x40, 0x40, 0x40, 0x20, 0x10, 0x00,  // 40
	0x40, 0x20, 0x10, 0x10, 0x10, 0x20, 0x40, 0x00,  // 41
	0x20, 0xA8, 0x70, 0x20, 0x70, 0xA8, 0x20, 0x00,  // 42
	0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00, 0x00,  // 43
	0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x40,  // 44
	0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00,  // 45
	0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x00,  // 46
	0x00, 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00,  // 47
	0x70, 0x88, 0x98, 0xA8, 0xC8, 0x88, 0x70, 0x00,  // 48
	0x20, 0x60, 0xA0, 0x20, 0x20, 0x20, 0xF8, 0x00,  // 49
	0x70, 0x88, 0x08, 0x10, 0x60, 0x80, 0xF8, 0x00,  // 50
	0x70, 0x88, 0x08, 0x30, 0x08, 0x88, 0x70, 0x00,  // 51
	0x10, 0x30, 0x50, 0x90, 0xF8, 0x10, 0x10, 0x00,  // 52
	0xF8, 0x80, 0xE0, 0x10, 0x08, 0x10, 0xE0, 0x00,  // 53
	0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70, 0x00,  // 54
	0xF8, 0x88, 0x10, 0x20, 0x20, 0x20, 0x20, 0x00,  // 55
	0x70, 0x88, 0x88, 0x70, 0x88, 0x88, 0x70, 0x00,  // 56
	0x70, 0x88, 0x88, 0x78, 0x08, 0x10, 0x60, 0x00,  // 57
	0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00,  // 58
	0x00, 0x00, 0x20, 0x00, 0x00, 0x20, 0x20, 0x40,  // 59
	0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00,  // 60
	0x00, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00, 0x00,  // 61
	0xC0, 0x60, 0x30, 0x18, 0x30, 0x60, 0xC0, 0x00,  // 62
	0x70, 0x88, 0x08, 0x10, 0x20, 0x00, 0x20, 0x00,  // 63
	0x70, 0x88, 0x08, 0x68, 0xA8, 0xA8, 0x70, 0x00,  // 64
	0x20, 0x50, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x00,  // 65
	0xF0, 0x48, 0x48, 0x70, 0x48, 0x48, 0xF0, 0x00,  // 66
	0x30, 0x48, 0x80, 0x80, 0x80, 0x48, 0x30, 0x00,  // 67
	0xE0, 0x50, 0x48, 0x48, 0x48, 0x50, 0xE0, 0x00,  // 68
	0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0xF8, 0x00,  // 69
	0xF8, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00,  // 70
	0x70, 0x88, 0x80, 0xB8, 0x88, 0x88, 0x70, 0x00,  // 71
	0x88, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x88, 0x00,  // 72
	0x70, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00,  // 73
	0x38, 0x10, 0x10, 0x10, 0x90, 0x90, 0x60, 0x00,  // 74
	0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88, 0x00,  // 75
	0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF8, 0x00,  // 76
	0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88, 0x00,  // 77
	0x88, 0xC8, 0xC8, 0xA8, 0x98, 0x98, 0x88, 0x00,  // 78
	0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00,  // 79
	0xF0, 0x88, 0x88, 0xF0, 0x80, 0x80, 0x80, 0x00,  // 80
	0x70, 0x88, 0x88, 0x88, 0xA8, 0x90, 0x68, 0x00,  // 81
	0xF0, 0x88, 0x88, 0xF0, 0xA0, 0x90, 0x88, 0x00,  // 82
	0x70, 0x88, 0x80, 0x70, 0x08, 0x88, 0x70, 0x00,  // 83
	0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00,  // 84
	0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00,  // 85
	0x88, 0x88, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00,  // 86
	0x88, 0x88, 0x88, 0xA8, 0xA8, 0xD8, 0x88, 0x00,  // 87
	0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00,  // 88
	0x88, 0x88, 0x88, 0x70, 0x20, 0x20, 0x20, 0x00,  // 89
	0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8, 0x00,  // 90
	0x70, 0x40, 0x40, 0x40, 0x40, 0x40, 0x70, 0x00,  // 91
	0x00, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x00,  // 92
	0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00,  // 93
	0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00,  // 94
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00,  // 95
	0x40, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,  // 96
	0x00, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 97
	0x80, 0x80, 0xB0, 0xC8, 0x88, 0xC8, 0xB0, 0x00,  // 98
	0x00, 0x00, 0x70, 0x88, 0x80, 0x88, 0x70, 0x00,  // 99
	0x08, 0x08, 0x68, 0x98, 0x88, 0x98, 0x68, 0x00,  // 100
	0x00, 0x00, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00,  // 101
	0x10, 0x28, 0x20, 0xF8, 0x20, 0x20, 0x20, 0x00,  // 102
	0x00, 0x00, 0x68, 0x98, 0x98, 0x68, 0x08, 0x70,  // 103
	0x80, 0x80, 0xF0, 0x88, 0x88, 0x88, 0x88, 0x00,  // 104
	0x20, 0x00, 0x60, 0x20, 0x20, 0x20, 0x70, 0x00,  // 105
	0x10, 0x00, 0x30, 0x10, 0x10, 0x10, 0x90, 0x60,  // 106
	0x40, 0x40, 0x48, 0x50, 0x60, 0x50, 0x48, 0x00,  // 107
	0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00,  // 108
	0x00, 0x00, 0xD0, 0xA8, 0xA8, 0xA8, 0xA8, 0x00,  // 109
	0x00, 0x00, 0xB0, 0xC8, 0x88, 0x88, 0x88, 0x00,  // 110
	0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00,  // 111
	0x00, 0x00, 0xB0, 0xC8, 0xC8, 0xB0, 0x80, 0x80,  // 112
	0x00, 0x00, 0x68, 0x98, 0x98, 0x68, 0x08, 0x08,  // 113
	0x00, 0x00, 0xB0, 0xC8, 0x80, 0x80, 0x80, 0x00,  // 114
	0x00, 0x00, 0x78, 0x80, 0xF0, 0x08, 0xF0, 0x00,  // 115
	0x40, 0x40, 0xF0, 0x40, 0x40, 0x48, 0x30, 0x00,  // 116
	0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x68, 0x00,  // 117
	0x00, 0x00, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00,  // 118
	0x00, 0x00, 0x88, 0xA8, 0xA8, 0xA8, 0x50, 0x00,  // 119
	0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00,  // 120
	0x00, 0x00, 0x88, 0x88, 0x98, 0x68, 0x08, 0x70,  // 121
	0x00, 0x00, 0xF8, 0x10, 0x20, 0x40, 0xF8, 0x00,  // 122
	0x18, 0x20, 0x20, 0x40, 0x20, 0x20, 0x18, 0x00,  // 123
	0x20, 0x20, 0x20, 0x00, 0x20, 0x20, 0x20, 0x00,  // 124
	0xC0, 0x20, 0x20, 0x10, 0x20, 0x20, 0xC0, 0x00,  // 125
	0x40, 0xA8, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,  // 126
	0x00, 0x00, 0x20, 0x50, 0xF8, 0x00, 0x00, 0x00,  // 127
	0x70, 0x88, 0x80, 0x80, 0x88, 0x70, 0x20, 0x60,  // 128
	0x90, 0x00, 0x00, 0x90, 0x90, 0x90, 0x68, 0x00,  // 129
	0x10, 0x20, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00,  // 130
	0x20, 0x50, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 131
	0x48, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 132
	0x20, 0x10, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 133
	0x20, 0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 134
	0x00, 0x70, 0x80, 0x80, 0x80, 0x70, 0x10, 0x60,  // 135
	0x20, 0x50, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00,  // 136
	0x50, 0x00, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00,  // 137
	0x20, 0x10, 0x70, 0x88, 0xF8, 0x80, 0x70, 0x00,  // 138
	0x50, 0x00, 0x00, 0x60, 0x20, 0x20, 0x70, 0x00,  // 139
	0x20, 0x50, 0x00, 0x60, 0x20, 0x20, 0x70, 0x00,  // 140
	0x40, 0x20, 0x00, 0x60, 0x20, 0x20, 0x70, 0x00,  // 141
	0x50, 0x00, 0x20, 0x50, 0x88, 0xF8, 0x88, 0x00,  // 142
	0x20, 0x00, 0x20, 0x50, 0x88, 0xF8, 0x88, 0x00,  // 143
	0x10, 0x20, 0xF8, 0x80, 0xF0, 0x80, 0xF8, 0x00,  // 144
	0x00, 0x00, 0x6C, 0x12, 0x7E, 0x90, 0x6E, 0x00,  // 145
	0x3E, 0x50, 0x90, 0x9C, 0xF0, 0x90, 0x9E, 0x00,  // 146
	0x60, 0x90, 0x00, 0x60, 0x90, 0x90, 0x60, 0x00,  // 147
	0x90, 0x00, 0x00, 0x60, 0x90, 0x90, 0x60, 0x00,  // 148
	0x40, 0x20, 0x00, 0x60, 0x90, 0x90, 0x60, 0x00,  // 149
	0x40, 0xA0, 0x00, 0xA0, 0xA0, 0xA0, 0x50, 0x00,  // 150
	0x40, 0x20, 0x00, 0xA0, 0xA0, 0xA0, 0x50, 0x00,  // 151
	0x90, 0x00, 0x90, 0x90, 0xB0, 0x50, 0x10, 0xE0,  // 152
	0x50, 0x00, 0x70, 0x88, 0x88, 0x88, 0x70, 0x00,  // 153
	0x50, 0x00, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00,  // 154
	0x20, 0x20, 0x78, 0x80, 0x80, 0x78, 0x20, 0x20,  // 155
	0x18, 0x24, 0x20, 0xF8, 0x20, 0xE2, 0x5C, 0x00,  // 156
	0x88, 0x50, 0x20, 0xF8, 0x20, 0xF8, 0x20, 0x00,  // 157
	0xC0, 0xA0, 0xA0, 0xC8, 0x9C, 0x88, 0x88, 0x8C,  // 158
	0x18, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x20, 0x40,  // 159
	0x10, 0x20, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 160
	0x10, 0x20, 0x00, 0x60, 0x20, 0x20, 0x70, 0x00,  // 161
	0x20, 0x40, 0x00, 0x60, 0x90, 0x90, 0x60, 0x00,  // 162
	0x20, 0x40, 0x00, 0x90, 0x90, 0x90, 0x68, 0x00,  // 163
	0x50, 0xA0, 0x00, 0xA0, 0xD0, 0x90, 0x90, 0x00,  // 164
	0x28, 0x50, 0x00, 0xC8, 0xA8, 0x98, 0x88, 0x00,  // 165
	0x00, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00, 0xF8,  // 166
	0x00, 0x60, 0x90, 0x90, 0x90, 0x60, 0x00, 0xF0,  // 167
	0x20, 0x00, 0x20, 0x40, 0x80, 0x88, 0x70, 0x00,  // 168
	0x00, 0x00, 0x00, 0xF8, 0x80, 0x80, 0x00, 0x00,  // 169
	0x00, 0x00, 0x00, 0xF8, 0x08, 0x08, 0x00, 0x00,  // 170
	0x84, 0x88, 0x90, 0xA8, 0x54, 0x84, 0x08, 0x1C,  // 171
	0x84, 0x88, 0x90, 0xA8, 0x58, 0xA8, 0x3C, 0x08,  // 172
	0x20, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00,  // 173
	0x00, 0x00, 0x24, 0x48, 0x90, 0x48, 0x24, 0x00,  // 174
	0x00, 0x00, 0x90, 0x48, 0x24, 0x48, 0x90, 0x00,  // 175
	0x28, 0x50, 0x20, 0x50, 0x88, 0xF8, 0x88, 0x00,  // 176
	0x28, 0x50, 0x70, 0x08, 0x78, 0x88, 0x78, 0x00,  // 177
	0x28, 0x50, 0x00, 0x70, 0x20, 0x20, 0x70, 0x00,  // 178
	0x28, 0x50, 0x00, 0x20, 0x20, 0x20, 0x70, 0x00,  // 179
	0x28, 0x50, 0x00, 0x70, 0x88, 0x88, 0x70, 0x00,  // 180
	0x50, 0xA0, 0x00, 0x60, 0x90, 0x90, 0x60, 0x00,  // 181
	0x28, 0x50, 0x00, 0x88, 0x88, 0x88, 0x70, 0x00,  // 182
	0x50, 0xA0, 0x00, 0xA0, 0xA0, 0xA0, 0x50, 0x00,  // 183
	0xFC, 0x48, 0x48, 0x48, 0xE8, 0x08, 0x50, 0x20,  // 184
	0x00, 0x50, 0x00, 0x50, 0x50, 0x50, 0x10, 0x20,  // 185
	0xC0, 0x44, 0xC8, 0x54, 0xEC, 0x54, 0x9E, 0x04,  // 186
	0x10, 0xA8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,  // 187
	0x00, 0x20, 0x50, 0x88, 0x50, 0x20, 0x00, 0x00,  // 188
	0x88, 0x10, 0x20, 0x40, 0x80, 0x28, 0x00, 0x00,  // 189
	0x7C, 0xA8, 0xA8, 0x68, 0x28, 0x28, 0x28, 0x00,  // 190
	0x38, 0x40, 0x30, 0x48, 0x48, 0x30, 0x08, 0x70,  // 191
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,  // 192
	0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F,  // 193
	0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  // 194
	0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 195
	0x00, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x00,  // 196
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,  // 197
	0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,  // 198
	0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0,  // 199
	0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC,  // 200
	0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,  // 201
	0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,  // 202
	0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88,  // 203
	0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11,  // 204
	0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,  // 205
	0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE,  // 206
	0x80, 0xC0, 0xE0, 0xF0, 0xE0, 0xC0, 0x80, 0x00,  // 207
	0x01, 0x03, 0x07, 0x0F, 0x07, 0x03, 0x01, 0x00,  // 208
	0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x3C, 0x7E, 0xFF,  // 209
	0x81, 0xC3, 0xE7, 0xFF, 0xFF, 0xE7, 0xC3, 0x81,  // 210
	0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00,  // 211
	0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F,  // 212
	0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00,  // 213
	0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0,  // 214
	0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC,  // 215
	0x00, 0x20, 0x20, 0x50, 0x50, 0x88, 0xF8, 0x00,  // 216
	0x20, 0x20, 0x70, 0x20, 0x70, 0x20, 0x20, 0x00,  // 217
	0x00, 0x00, 0x00, 0x50, 0x88, 0xA8, 0x50, 0x00,  // 218
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,  // 219
	0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,  // 220
	0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,  // 221
	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 222
	0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,  // 223
	0x00, 0x00, 0x68, 0x90, 0x90, 0x90, 0x68, 0x00,  // 224
	0x30, 0x48, 0x48, 0x70, 0x48, 0x48, 0x70, 0xC0,  // 225
	0xF8, 0x88, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00,  // 226
	0xF8, 0x50, 0x50, 0x50, 0x50, 0x50, 0x98, 0x00,  // 227
	0xF8, 0x88, 0x40, 0x20, 0x40, 0x88, 0xF8, 0x00,  // 228
	0x00, 0x00, 0x78, 0x90, 0x90, 0x90, 0x60, 0x00,  // 229
	0x00, 0x50, 0x50, 0x50, 0x50, 0x68, 0x80, 0x80,  // 230
	0x00, 0x50, 0xA0, 0x20, 0x20, 0x20, 0x20, 0x00,  // 231
	0xF8, 0x20, 0x70, 0xA8, 0xA8, 0x70, 0x20, 0xF8,  // 232
	0x20, 0x50, 0x88, 0xF8, 0x88, 0x50, 0x20, 0x00,  // 233
	0x70, 0x88, 0x88, 0x88, 0x50, 0x50, 0xD8, 0x00,  // 234
	0x30, 0x40, 0x40, 0x20, 0x50, 0x50, 0x50, 0x20,  // 235
	0x00, 0x00, 0x00, 0x50, 0xA8, 0xA8, 0x50, 0x00,  // 236
	0x08, 0x70, 0xA8, 0xA8, 0xA8, 0x70, 0x80, 0x00,  // 237
	0x38, 0x40, 0x80, 0xF8, 0x80, 0x40, 0x38, 0x00,  // 238
	0x70, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x00,  // 239
	0x00, 0xF8, 0x00, 0xF8, 0x00, 0xF8, 0x00, 0x00,  // 240
	0x20, 0x20, 0xF8, 0x20, 0x20, 0x00, 0xF8, 0x00,  // 241
	0xC0, 0x30, 0x08, 0x30, 0xC0, 0x00, 0xF8, 0x00,  // 242
	0x18, 0x60, 0x80, 0x60, 0x18, 0x00, 0xF8, 0x00,  // 243
	0x10, 0x28, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,  // 244
	0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xA0, 0x40,  // 245
	0x00, 0x20, 0x00, 0xF8, 0x00, 0x20, 0x00, 0x00,  // 246
	0x00, 0x50, 0xA0, 0x00, 0x50, 0xA0, 0x00, 0x00,  // 247
	0x00, 0x18, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00,  // 248
	0x00, 0x30, 0x78, 0x78, 0x30, 0x00, 0x00, 0x00,  // 249
	0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,  // 250
	0x3E, 0x20, 0x20, 0x20, 0xA0, 0x60, 0x20, 0x00,  // 251
	0xA0, 0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00,  // 252
	0x40, 0xA0, 0x20, 0x40, 0xE0, 0x00, 0x00, 0x00,  // 253
	0x00, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x00,  // 254
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  // 255
};

static byte MSXFont[256 * 9];

ImagePrinterMSX::ImagePrinterMSX(MSXMotherBoard& motherBoard_)
	: ImagePrinter(motherBoard_, true)
{
	msxPrnSetFont(MSXFontRaw);
	resetEmulatedPrinter();
}

const string& ImagePrinterMSX::getName() const
{
	static const string name("msx-printer");
	return name;
}

std::string_view ImagePrinterMSX::getDescription() const
{
	// TODO which printer type
	return "Emulate MSX printer, prints to image.";
}

void ImagePrinterMSX::msxPrnSetFont(const byte* msxBits)
{
	// Convert MSX printer font to Epson printer font
	byte* font = MSXFont;

	for (int i = 0; i < 256; ++i) {
		byte oneBits = 0;
		int start = -1;
		int end = 0;

		// Rotate MSX character
		for (int j = 0; j < 8; ++j) {
			byte charBits = 0;
			for (int k = 0; k < 8; ++k) {
				charBits |= ((msxBits[7 - k] >> (7 - j)) & 1) << k;
			}

			oneBits |= charBits;
			if (oneBits  != 0 && start < 0) start = j;
			if (charBits != 0) end = j;
			font[j + 1] = charBits;
		}

		end = end + 1;
		if (start < 0 || start >= 7) start = 0;
		if (end == 1) end = 5;
		if (end >= 7) end = 7;
		font[0] = (start << 4) | end;

		font    += 9;
		msxBits += 8;
	}
}

void ImagePrinterMSX::getNumberOfDots(unsigned& dotsX, unsigned& dotsY)
{
	dotsX = 825;
	dotsY = 825;
}

void ImagePrinterMSX::resetSettings()
{
	lineFeed     = 12.0;
	leftBorder   = 48;
	rightBorder  = 800;
	graphDensity = 1.0;
	fontDensity  = 1.0;
	pageTop      = 48;
	lines        = 72;
	fontWidth    = 8;
	eightBit     = -1;

	// note: this only overwrites 9/12 of the fontInfo.rom array.
	memcpy(fontInfo.rom, MSXFont, sizeof(MSXFont));
	fontInfo.charWidth = 9;
	fontInfo.pixelDelta = 1.0;
	fontInfo.useRam = false;
}


unsigned ImagePrinterMSX::calcEscSequenceLength(byte character)
{
	switch (character) {
		case 'C':
			return 1;
		case 'T': case 'Z':
			return 2;
		case 'O': case '\\': case 'L': case '/':
			return 3;
		case 'S':
			return 4;
		case 'G':
			return 7;
		default:
			return 0;
	}
}

unsigned ImagePrinterMSX::parseNumber(unsigned sizeStart, unsigned sizeChars)
{
	unsigned Value = 0;
	while (sizeChars--) {
		Value = Value * 10;
		byte data = abEscSeq[sizeStart++];
		if (data >= '0' && data <= '9') {
			Value += unsigned(data - '0');
		}
	}
	return Value;
}

void ImagePrinterMSX::processEscSequence()
{
	switch (abEscSeq[0]) {
		case 'N':
			proportional = false;
			fontDensity = 1.0;
			break;
		case 'E':
			proportional = false;
			fontDensity = 1.40;
			break;
		case 'Q':
			proportional = false;
			fontDensity = 1.72;
			break;
		case 'P':
			proportional = true;
			fontDensity = 0.90;
			break;
		case '!':
			letterQuality = true;
			break;
		case '\"':
			letterQuality = false;
			break;
		case 'C':
			switch (abEscSeq[1]) {
				case 'D':
					doubleStrike = true;
					break;
				case 'd':
					doubleStrike = false;
					break;
				case 'I':
					italic = true;
					break;
				case 'i':
					italic = false;
					break;
				case 'B':
					bold = true;
					break;
				case 'b':
					bold = false;
					break;
				case 'S':
					superscript = true;
					break;
				case 's':
					superscript = false;
					break;
				case 'U':
					subscript = true;
					break;
				case 'u':
					subscript = false;
					break;
			}
			break;
		case '(': // ???: Set a horizontal tab position
			break;
		case ')': // ???: Partially delete a horizontal tab position
			break;
		case '2': // ???: Clear horizontal tabs
			break;
		case 'O':
			switch (abEscSeq[1]) {
				case 'S':
					perforationSkip = parseNumber(2, 2);
					break;
				case 'I': // ???: Set page-height(inches)
					break;
				default: // ???: Set page-height (lines)
					break;
			}
			break;
		case '/': // Right margin
			break;
		case 'L':
			leftBorder = parseNumber(1, 3);
			break;
		case 'A': // ???: Line-feed 1/6"
			lineFeed = 12.0;
			break;
		case 'B': // ???: Line-feed 1/9"
			lineFeed = 8.0;
			break;
		case 'T': // ???: Line-feed nn/144"
			lineFeed = parseNumber(1, 2) / 2.0;
			break;
		case 'Z': // ???: Line-feed nn/216"
			lineFeed = parseNumber(1, 2) / 3.0;
			break;
		case '[': // ???: Uni-directional printing
			break;
		case ']': // ???: Bi-directional printing
			break;
		case 'p':
			detectPaperOut = true;
			break;
		case 'q':
			detectPaperOut = false;
			break;
		case 13: // (ESC, CR) Move printer-head to end-position
			break;
		case '@':
			resetEmulatedPrinter();
			break;
		case '\\':
			rightBorder = parseNumber(1, 3);
			break;
		case 'G':
			graphDensity = parseNumber(1, 3) / 100.0;
			if (graphDensity < 0.1) {
				graphDensity = 0.1;
			}
			sizeRemainingDataBytes = parseNumber(4, 4);
			break;
		case 'S': // Print graphics, density depending on font
			sizeRemainingDataBytes = parseNumber(1, 4);
			break;
		case 'X':
			underline = true;
			break;
		case 'Y':
			underline = false;
			break;
		case '&':
		case '$':
			japanese = !japanese;
			break;
		case 'f': // ???: Scroll paper forward
			break;
		case 'r': // ???: Scroll paper back
			break;
	}
}

void ImagePrinterMSX::processCharacter(byte data)
{
	if (alternateChar) {
		// Print SOH-preceded character
		printVisibleCharacter(data & 0x1F);
		alternateChar = false;
	} else {
		switch (data) {
			case 1: // SOH: A symbolcode preceding code
				alternateChar = true;
				break;
			case 7: // BEL: Audible beep (buzzer, 0.3s)
				break;
			case 8: // BS: Backstep (1 Character)
				// TODO: fix for other font-sizes
				hpos -= 8;
				if (hpos < leftBorder) {
					hpos = leftBorder;
				}
				break;
			case 9: // HAT: Horizontal tabulator
				// TODO: fix for other font-sizes
				hpos = ((unsigned(hpos) + 64 - leftBorder) & ~63)
				     + leftBorder;
				if (hpos < rightBorder) {
					break;
				}
				hpos = leftBorder;
				[[fallthrough]];
			case 10: // LF: Carriage return + Line feed
			case 11: // VT: Vertical tabulator (like LF)
				//hpos = leftBorder;
				vpos += lineFeed;
				if (vpos >= pageHeight) {
					flushEmulatedPrinter();
				}
				break;
			case 12: // FF: Form feed
				ensurePrintPage();
				flushEmulatedPrinter();
				break;
			case 13: // CR: Carriage return
				hpos = leftBorder;
				break;
			case 14: // SO: Double character-width on
				doubleWidth = true;
				break;
			case 15: // SI: Double character-width off
				doubleWidth = false;
				break;
			case 27:
				escSequence = true;
				break;
			default:
				if (data >= 32) {
					// Yes, we can print it!
					printVisibleCharacter(data);
				}
				break;
		}
	}
}

template<typename Archive>
void ImagePrinterMSX::serialize(Archive& /*ar*/, unsigned /*version*/)
{
	// TODO is this worth it?
}
INSTANTIATE_SERIALIZE_METHODS(ImagePrinterMSX);
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, ImagePrinterMSX, "ImagePrinterMSX");


// class ImagePrinterEpson

constexpr byte EpsonFontRom[] = {
	0x8b, 0x04, 0x0a, 0x20, 0x8a, 0x60, 0x0a, 0x20, 0x1c, 0x02, 0x00, 0x00, //   0
	0x8b, 0x1c, 0x22, 0x08, 0xa2, 0x48, 0x22, 0x08, 0x22, 0x18, 0x00, 0x00, //   1
	0x9b, 0x00, 0x3c, 0x00, 0x82, 0x40, 0x02, 0x00, 0x3c, 0x02, 0x00, 0x00, //   2
	0x9a, 0x00, 0x1c, 0x22, 0x80, 0x62, 0x00, 0x22, 0x1c, 0x00, 0x00, 0x00, //   3
	0x96, 0x00, 0x12, 0x80, 0x5e, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, //   4
	0xa7, 0x00, 0x00, 0x40, 0xa0, 0x00, 0xa0, 0x40, 0x00, 0x00, 0x00, 0x00, //   5
	0x8b, 0x12, 0x00, 0x7e, 0x80, 0x12, 0x80, 0x02, 0x80, 0x42, 0x00, 0x00, //   6
	0xc8, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //   7
	0x8b, 0x06, 0x00, 0x09, 0x00, 0x51, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, //   8
	0x8b, 0x5e, 0x80, 0x10, 0x80, 0x08, 0x40, 0x04, 0x40, 0x9e, 0x00, 0x00, //   9
	0x8a, 0x40, 0x9e, 0x00, 0x90, 0x40, 0x10, 0x4e, 0x80, 0x00, 0x00, 0x00, //  10
	0x8b, 0x92, 0x28, 0x44, 0x00, 0x44, 0x00, 0x44, 0x28, 0x92, 0x00, 0x00, //  11
	0x8b, 0xfe, 0x00, 0xa0, 0x00, 0x48, 0x00, 0x1e, 0x00, 0x0a, 0x00, 0x00, //  12
	0x8b, 0x06, 0x08, 0x54, 0xa0, 0x04, 0xa0, 0x54, 0x08, 0x06, 0x00, 0x00, //  13
	0x8b, 0x04, 0x0a, 0x20, 0x0a, 0xa0, 0x0a, 0x20, 0x1c, 0x02, 0x00, 0x00, //  14
	0x0a, 0x38, 0x44, 0x01, 0x44, 0x01, 0x46, 0x00, 0x44, 0x00, 0x00, 0x00, //  15
	0x9a, 0x00, 0x50, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x14, 0x00, 0x00, 0x00, //  16
	0x8a, 0x7e, 0x80, 0x00, 0x80, 0x12, 0x80, 0x12, 0x6c, 0x00, 0x00, 0x00, //  17
	0x8b, 0x3e, 0x40, 0x90, 0x00, 0xfe, 0x00, 0x92, 0x00, 0x92, 0x00, 0x00, //  18
	0x8b, 0x2c, 0x02, 0x28, 0x02, 0x1c, 0x20, 0x0a, 0x20, 0x1a, 0x00, 0x00, //  19
	0x8b, 0x3a, 0x44, 0x00, 0x8a, 0x10, 0xa2, 0x00, 0x44, 0xb8, 0x00, 0x00, //  20
	0x8b, 0x02, 0x08, 0x14, 0x22, 0x08, 0x22, 0x14, 0x08, 0x20, 0x00, 0x00, //  21
	0xa9, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, //  22
	0x8b, 0x06, 0x88, 0x14, 0x20, 0x44, 0x20, 0x14, 0x88, 0x06, 0x00, 0x00, //  23
	0x8b, 0x1c, 0xa2, 0x00, 0x22, 0x00, 0x22, 0x00, 0xa2, 0x1c, 0x00, 0x00, //  24
	0x8b, 0x3c, 0x82, 0x00, 0x02, 0x00, 0x02, 0x00, 0x82, 0x3c, 0x00, 0x00, //  25
	0x8b, 0x04, 0x0a, 0xa0, 0x0a, 0x20, 0x0a, 0xa0, 0x1c, 0x02, 0x00, 0x00, //  26
	0x9a, 0x00, 0x1c, 0xa2, 0x00, 0x22, 0x00, 0xa2, 0x1c, 0x00, 0x00, 0x00, //  27
	0x8a, 0x3c, 0x80, 0x02, 0x00, 0x02, 0x80, 0x3c, 0x02, 0x00, 0x00, 0x00, //  28
	0x8b, 0x3e, 0x00, 0x2a, 0x00, 0x6a, 0x80, 0x2a, 0x00, 0x22, 0x00, 0x00, //  29
	0x8b, 0x1c, 0x22, 0x08, 0x22, 0x48, 0xa2, 0x08, 0x22, 0x18, 0x00, 0x00, //  30
	0x8b, 0xa8, 0x00, 0x68, 0x00, 0x3e, 0x00, 0x68, 0x00, 0xa8, 0x00, 0x00, //  31
	0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  32
	0xc8, 0x00, 0x00, 0x00, 0x00, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  33
	0xa9, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, //  34
	0x8b, 0x28, 0x00, 0xfe, 0x00, 0x28, 0x00, 0xfe, 0x00, 0x28, 0x00, 0x00, //  35
	0x8b, 0x24, 0x00, 0x54, 0x00, 0xfe, 0x00, 0x54, 0x00, 0x48, 0x00, 0x00, //  36
	0x8b, 0xc0, 0x02, 0xc4, 0x08, 0x10, 0x20, 0x46, 0x80, 0x06, 0x00, 0x00, //  37
	0x8b, 0x4c, 0xa0, 0x12, 0xa0, 0x4a, 0x00, 0x04, 0x08, 0x12, 0x00, 0x00, //  38
	0xc8, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, //  39
	0xc9, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00, //  40
	0xa7, 0x00, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  41
	0x8b, 0x10, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x10, 0x00, 0x00, //  42
	0x8b, 0x10, 0x00, 0x10, 0x00, 0x7c, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, //  43
	0x39, 0x00, 0x00, 0x00, 0x0c, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, //  44
	0x8b, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, //  45
	0xa8, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  46
	0x9a, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, //  47
	0x8b, 0x38, 0x44, 0x00, 0x82, 0x00, 0x82, 0x00, 0x44, 0x38, 0x00, 0x00, //  48
	0xa9, 0x00, 0x00, 0x42, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, //  49
	0x8b, 0x42, 0x80, 0x06, 0x80, 0x0a, 0x80, 0x12, 0x80, 0x62, 0x00, 0x00, //  50
	0x8b, 0x84, 0x00, 0x82, 0x00, 0xa2, 0x00, 0xd2, 0x00, 0x8c, 0x00, 0x00, //  51
	0x8b, 0x08, 0x10, 0x28, 0x40, 0x88, 0x00, 0xfe, 0x00, 0x08, 0x00, 0x00, //  52
	0x8b, 0xe4, 0x02, 0xa0, 0x02, 0xa0, 0x02, 0xa0, 0x02, 0x9c, 0x00, 0x00, //  53
	0x8b, 0x0c, 0x12, 0x20, 0x52, 0x80, 0x12, 0x00, 0x12, 0x0c, 0x00, 0x00, //  54
	0x8b, 0x80, 0x00, 0x82, 0x04, 0x88, 0x10, 0xa0, 0x40, 0x80, 0x00, 0x00, //  55
	0x8b, 0x6c, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x6c, 0x00, 0x00, //  56
	0x8b, 0x60, 0x90, 0x00, 0x90, 0x02, 0x94, 0x08, 0x90, 0x60, 0x00, 0x00, //  57
	0xa7, 0x00, 0x00, 0x36, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  58
	0x27, 0x00, 0x00, 0x6d, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  59
	0x89, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, //  60
	0x8b, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x28, 0x00, 0x00, //  61
	0xab, 0x00, 0x00, 0x82, 0x00, 0x44, 0x00, 0x28, 0x00, 0x10, 0x00, 0x00, //  62
	0x8b, 0x40, 0x80, 0x00, 0x80, 0x0a, 0x80, 0x10, 0x80, 0x60, 0x00, 0x00, //  63
	0x8b, 0x38, 0x44, 0x82, 0x10, 0xaa, 0x00, 0xaa, 0x00, 0x7a, 0x00, 0x00, //  64
	0x8b, 0x1e, 0x20, 0x48, 0x80, 0x08, 0x80, 0x48, 0x20, 0x1e, 0x00, 0x00, //  65
	0x8b, 0x82, 0x7c, 0x82, 0x10, 0x82, 0x10, 0x82, 0x10, 0x6c, 0x00, 0x00, //  66
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x44, 0x00, 0x00, //  67
	0x8b, 0x82, 0x7c, 0x82, 0x00, 0x82, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00, //  68
	0x8b, 0xfe, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x82, 0x00, 0x00, //  69
	0x8b, 0xfe, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x80, 0x00, 0x00, //  70
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x10, 0x82, 0x10, 0x82, 0x5c, 0x00, 0x00, //  71
	0x8b, 0xfe, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0xfe, 0x00, 0x00, //  72
	0xa9, 0x00, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, //  73
	0x8a, 0x0c, 0x02, 0x00, 0x82, 0x00, 0x82, 0x7c, 0x80, 0x00, 0x00, 0x00, //  74
	0x8b, 0xfe, 0x00, 0x10, 0x00, 0x28, 0x00, 0x44, 0x00, 0x82, 0x00, 0x00, //  75
	0x8b, 0xfe, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, //  76
	0x8b, 0xfe, 0x00, 0x40, 0x20, 0x10, 0x20, 0x40, 0x00, 0xfe, 0x00, 0x00, //  77
	0x8b, 0xfe, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0xfe, 0x00, 0x00, //  78
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x00, 0x82, 0x00, 0x82, 0x7c, 0x00, 0x00, //  79
	0x8b, 0xfe, 0x00, 0x90, 0x00, 0x90, 0x00, 0x90, 0x00, 0x60, 0x00, 0x00, //  80
	0x8b, 0x7c, 0x82, 0x00, 0x82, 0x08, 0x82, 0x04, 0x80, 0x7a, 0x00, 0x00, //  81
	0x8b, 0xfe, 0x00, 0x90, 0x00, 0x90, 0x00, 0x98, 0x04, 0x62, 0x00, 0x00, //  82
	0x8b, 0x64, 0x92, 0x00, 0x92, 0x00, 0x92, 0x00, 0x92, 0x4c, 0x00, 0x00, //  83
	0x8b, 0x80, 0x00, 0x80, 0x00, 0xfe, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, //  84
	0x8b, 0xfc, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xfc, 0x00, 0x00, //  85
	0x8b, 0xe0, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0xe0, 0x00, 0x00, //  86
	0x8b, 0xfc, 0x02, 0x04, 0x08, 0x30, 0x08, 0x04, 0x02, 0xfc, 0x00, 0x00, //  87
	0x9a, 0x00, 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00, //  88
	0x8b, 0x80, 0x40, 0x20, 0x10, 0x0e, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, //  89
	0x9a, 0x00, 0x82, 0x04, 0x8a, 0x10, 0xa2, 0x40, 0x82, 0x00, 0x00, 0x00, //  90
	0xa9, 0x00, 0x00, 0xfe, 0x00, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, //  91
	0x9a, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00, 0x00, //  92
	0xa9, 0x00, 0x00, 0x82, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, //  93
	0x8b, 0x20, 0x00, 0x40, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00, 0x00, //  94
	0x0b, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, //  95
	0xb7, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  96
	0x8b, 0x04, 0x0a, 0x20, 0x0a, 0x20, 0x0a, 0x20, 0x1c, 0x02, 0x00, 0x00, //  97
	0x8a, 0xfe, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x1c, 0x00, 0x00, 0x00, //  98
	0x8a, 0x1c, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x00, 0x00, //  99
	0x8a, 0x1c, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0xfe, 0x00, 0x00, 0x00, // 100
	0x8b, 0x1c, 0x22, 0x08, 0x22, 0x08, 0x22, 0x08, 0x22, 0x18, 0x00, 0x00, // 101
	0x89, 0x10, 0x00, 0x10, 0x7e, 0x90, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, // 102
	0x0a, 0x38, 0x44, 0x01, 0x44, 0x01, 0x44, 0x01, 0x7e, 0x00, 0x00, 0x00, // 103
	0x8a, 0xfe, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x1e, 0x00, 0x00, 0x00, // 104
	0x98, 0x00, 0x22, 0x00, 0xbe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 105
	0x99, 0x00, 0x01, 0x00, 0x01, 0x20, 0x01, 0xbe, 0x00, 0x00, 0x00, 0x00, // 106
	0x9a, 0x00, 0xfe, 0x00, 0x08, 0x00, 0x14, 0x00, 0x22, 0x00, 0x00, 0x00, // 107
	0x98, 0x00, 0x82, 0x00, 0xfe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 108
	0x8b, 0x1e, 0x20, 0x00, 0x20, 0x1e, 0x20, 0x00, 0x20, 0x1e, 0x00, 0x00, // 109
	0x8a, 0x3e, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x1e, 0x00, 0x00, 0x00, // 110
	0x8b, 0x1c, 0x22, 0x00, 0x22, 0x00, 0x22, 0x00, 0x22, 0x1c, 0x00, 0x00, // 111
	0x0a, 0x7f, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x38, 0x00, 0x00, 0x00, // 112
	0x1b, 0x00, 0x38, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x7e, 0x00, 0x00, // 113
	0x8a, 0x3e, 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, // 114
	0x8b, 0x10, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x00, 0x2a, 0x04, 0x00, 0x00, // 115
	0x8a, 0x20, 0x00, 0x7c, 0x02, 0x20, 0x02, 0x20, 0x02, 0x00, 0x00, 0x00, // 116
	0x8b, 0x3c, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x3c, 0x02, 0x00, 0x00, // 117
	0x8b, 0x20, 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, // 118
	0x8b, 0x3c, 0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x3c, 0x00, 0x00, // 119
	0x89, 0x22, 0x14, 0x00, 0x08, 0x00, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, // 120
	0x0b, 0x40, 0x20, 0x11, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, // 121
	0x89, 0x22, 0x04, 0x22, 0x08, 0x22, 0x10, 0x22, 0x00, 0x00, 0x00, 0x00, // 122
	0xaa, 0x00, 0x00, 0x10, 0x00, 0x6c, 0x82, 0x00, 0x82, 0x00, 0x00, 0x00, // 123
	0xc7, 0x00, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 124
	0xaa, 0x00, 0x82, 0x00, 0x82, 0x6c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, // 125
	0x8b, 0x40, 0x80, 0x00, 0x80, 0x40, 0x20, 0x00, 0x20, 0x40, 0x00, 0x00, // 126
	0x8b, 0x7c, 0x82, 0x04, 0x8a, 0x10, 0xa2, 0x40, 0x82, 0x7c, 0x00, 0x00, // 127
	0x8a, 0x04, 0x0a, 0x80, 0x2a, 0x60, 0x0a, 0x24, 0x1a, 0x00, 0x00, 0x00, // 128
	0x8a, 0x0c, 0x12, 0x28, 0x82, 0x68, 0x02, 0x28, 0x10, 0x00, 0x00, 0x00, // 129
	0x8a, 0x0c, 0x32, 0x00, 0x82, 0x40, 0x02, 0x0c, 0x32, 0x00, 0x00, 0x00, // 130
	0x8a, 0x0c, 0x12, 0x00, 0xa0, 0x42, 0x00, 0x24, 0x18, 0x00, 0x00, 0x00, // 131
	0x98, 0x00, 0x02, 0x00, 0x16, 0x88, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, // 132
	0xa9, 0x00, 0x00, 0x40, 0xa0, 0x00, 0xa0, 0x40, 0x00, 0x00, 0x00, 0x00, // 133
	0x8b, 0x12, 0x00, 0x1e, 0x60, 0x12, 0x80, 0x12, 0x80, 0x40, 0x00, 0x00, // 134
	0x9a, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x80, 0x00, 0x00, 0x00, // 135
	0x8a, 0x06, 0x01, 0x08, 0x01, 0x10, 0x21, 0x80, 0x02, 0x00, 0x00, 0x00, // 136
	0x8b, 0x06, 0x58, 0x80, 0x08, 0x84, 0x40, 0x06, 0x58, 0x80, 0x00, 0x00, // 137
	0x8b, 0x12, 0x4c, 0x80, 0x10, 0x80, 0x50, 0x02, 0x4c, 0x80, 0x00, 0x00, // 138
	0x8b, 0x02, 0x18, 0x24, 0x80, 0x44, 0x02, 0x48, 0x30, 0x80, 0x00, 0x00, // 139
	0x8b, 0x06, 0x38, 0xc0, 0x20, 0x88, 0x26, 0xd8, 0x02, 0x08, 0x00, 0x00, // 140
	0x8b, 0x02, 0x04, 0x08, 0x14, 0x40, 0xa4, 0x00, 0xbe, 0x40, 0x00, 0x00, // 141
	0x8a, 0x04, 0x0a, 0x20, 0x0a, 0x20, 0x8a, 0x24, 0x1a, 0x00, 0x00, 0x00, // 142
	0x1b, 0x00, 0x18, 0x21, 0x04, 0x41, 0x06, 0x40, 0x04, 0x40, 0x00, 0x00, // 143
	0x8b, 0x02, 0x10, 0x6a, 0x00, 0xaa, 0x00, 0xac, 0x10, 0x80, 0x00, 0x00, // 144
	0x8a, 0x06, 0x18, 0x60, 0x00, 0x82, 0x10, 0x82, 0x6c, 0x00, 0x00, 0x00, // 145
	0x8b, 0x0e, 0x30, 0x40, 0x90, 0x0e, 0x70, 0x82, 0x10, 0x82, 0x00, 0x00, // 146
	0x8b, 0x04, 0x22, 0x08, 0x22, 0x1c, 0x22, 0x08, 0x22, 0x10, 0x00, 0x00, // 147
	0x8b, 0x1a, 0x24, 0x42, 0x08, 0x92, 0x20, 0x84, 0x48, 0xb0, 0x00, 0x00, // 148
	0x8a, 0x0c, 0x11, 0x02, 0x2c, 0x12, 0x20, 0x44, 0x18, 0x00, 0x00, 0x00, // 149
	0xa9, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, // 150
	0x8b, 0x02, 0x04, 0x08, 0x14, 0x80, 0x24, 0x00, 0x3e, 0x80, 0x00, 0x00, // 151
	0x8b, 0x0c, 0x12, 0x00, 0xa2, 0x00, 0x22, 0x00, 0xa4, 0x18, 0x00, 0x00, // 152
	0x8b, 0x0c, 0x32, 0x00, 0x82, 0x00, 0x02, 0x00, 0x8c, 0x30, 0x00, 0x00, // 153
	0x8a, 0x04, 0x0a, 0x20, 0x8a, 0x20, 0x0a, 0x24, 0x9a, 0x00, 0x00, 0x00, // 154
	0x8a, 0x0c, 0x12, 0x00, 0xa0, 0x02, 0x00, 0x24, 0x98, 0x00, 0x00, 0x00, // 155
	0x8b, 0x0c, 0x32, 0x80, 0x02, 0x00, 0x02, 0x0c, 0xb2, 0x00, 0x00, 0x00, // 156
	0x8b, 0x06, 0x18, 0x22, 0x08, 0x22, 0x48, 0x22, 0x80, 0x20, 0x00, 0x00, // 157
	0x8a, 0x0c, 0x12, 0x28, 0x02, 0x68, 0x02, 0xa8, 0x10, 0x00, 0x00, 0x00, // 158
	0x8b, 0x08, 0x20, 0x88, 0x66, 0x18, 0x20, 0x48, 0x20, 0x80, 0x00, 0x00, // 159
	0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 160
	0x9a, 0x00, 0x02, 0x00, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 161
	0x9a, 0x00, 0x20, 0x40, 0x80, 0x00, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 162
	0x8b, 0x28, 0x06, 0x38, 0xc0, 0x28, 0x06, 0x38, 0xc0, 0x28, 0x00, 0x00, // 163
	0x8a, 0x00, 0x24, 0x10, 0x46, 0x38, 0xc4, 0x10, 0x48, 0x00, 0x00, 0x00, // 164
	0x8b, 0x40, 0x82, 0x44, 0x88, 0x10, 0x22, 0x44, 0x82, 0x04, 0x00, 0x00, // 165
	0x8b, 0x0c, 0x10, 0x42, 0xa0, 0x12, 0xa8, 0x44, 0x0a, 0x10, 0x00, 0x00, // 166
	0xc8, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, // 167
	0xba, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, // 168
	0x98, 0x00, 0x02, 0x00, 0x04, 0x88, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, // 169
	0x8b, 0x10, 0x04, 0x50, 0x28, 0x10, 0x28, 0x14, 0x40, 0x10, 0x00, 0x00, // 170
	0x8b, 0x10, 0x00, 0x14, 0x08, 0x10, 0x20, 0x50, 0x00, 0x10, 0x00, 0x00, // 171
	0x29, 0x00, 0x00, 0x01, 0x04, 0x0a, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, // 172
	0x8b, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, // 173
	0xa8, 0x00, 0x00, 0x02, 0x04, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, // 174
	0x9a, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, // 175
	0x8b, 0x1c, 0x20, 0x42, 0x00, 0x82, 0x00, 0x84, 0x08, 0x70, 0x00, 0x00, // 176
	0x99, 0x00, 0x02, 0x00, 0x46, 0x18, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, // 177
	0x8b, 0x02, 0x40, 0x06, 0x80, 0x0a, 0x80, 0x12, 0x80, 0x60, 0x00, 0x00, // 178
	0x8b, 0x04, 0x00, 0x82, 0x00, 0x92, 0x00, 0xb2, 0x4c, 0x80, 0x00, 0x00, // 179
	0x8b, 0x08, 0x10, 0x08, 0x20, 0x08, 0x46, 0x38, 0xc0, 0x08, 0x00, 0x00, // 180
	0x8b, 0x04, 0x60, 0x82, 0x20, 0x82, 0x20, 0x84, 0x18, 0x80, 0x00, 0x00, // 181
	0x8a, 0x0c, 0x10, 0x22, 0x10, 0x42, 0x10, 0x82, 0x0c, 0x00, 0x00, 0x00, // 182
	0x8b, 0x80, 0x02, 0x84, 0x08, 0x90, 0x20, 0x80, 0x40, 0x80, 0x00, 0x00, // 183
	0x8b, 0x0c, 0x62, 0x10, 0x82, 0x10, 0x82, 0x10, 0x8c, 0x60, 0x00, 0x00, // 184
	0x8a, 0x60, 0x02, 0x90, 0x04, 0x90, 0x08, 0x90, 0x60, 0x00, 0x00, 0x00, // 185
	0xa9, 0x00, 0x00, 0x02, 0x14, 0x22, 0x14, 0x20, 0x00, 0x00, 0x00, 0x00, // 186
	0x2a, 0x00, 0x00, 0x01, 0x04, 0x2a, 0x44, 0x28, 0x40, 0x00, 0x00, 0x00, // 187
	0x9a, 0x00, 0x10, 0x08, 0x24, 0x02, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, // 188
	0x8a, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x20, 0x00, 0x00, 0x00, // 189
	0x9a, 0x00, 0x02, 0x00, 0x04, 0x80, 0x48, 0x20, 0x10, 0x00, 0x00, 0x00, // 190
	0x8a, 0x48, 0x02, 0x80, 0x08, 0x80, 0x10, 0x80, 0x60, 0x00, 0x00, 0x00, // 191
	0x8b, 0x1c, 0x20, 0x42, 0x80, 0x12, 0x88, 0x22, 0x88, 0x70, 0x00, 0x00, // 192
	0x8b, 0x02, 0x04, 0x08, 0x10, 0x28, 0x40, 0x88, 0x00, 0xfe, 0x00, 0x00, // 193
	0x8b, 0x06, 0x98, 0x62, 0x80, 0x12, 0x80, 0x12, 0x8c, 0x60, 0x00, 0x00, // 194
	0x8b, 0x1c, 0x22, 0x40, 0x82, 0x00, 0x82, 0x00, 0x84, 0x40, 0x00, 0x00, // 195
	0x8b, 0x06, 0x98, 0x62, 0x80, 0x02, 0x80, 0x04, 0x88, 0x70, 0x00, 0x00, // 196
	0x8b, 0x06, 0x38, 0xc2, 0x10, 0x82, 0x10, 0x82, 0x00, 0x80, 0x00, 0x00, // 197
	0x8b, 0x06, 0x38, 0xc0, 0x10, 0x80, 0x10, 0x80, 0x00, 0x80, 0x00, 0x00, // 198
	0x8b, 0x1c, 0x22, 0x40, 0x82, 0x00, 0x92, 0x04, 0x98, 0x40, 0x00, 0x00, // 199
	0x8b, 0x06, 0x38, 0xc0, 0x10, 0x00, 0x10, 0x06, 0x38, 0xc0, 0x00, 0x00, // 200
	0x92, 0x00, 0x02, 0x00, 0x86, 0x18, 0xe2, 0x00, 0x80, 0x00, 0x00, 0x00, // 201
	0x8b, 0x0c, 0x02, 0x00, 0x02, 0x80, 0x04, 0x98, 0x60, 0x80, 0x00, 0x00, // 202
	0x8b, 0x06, 0x38, 0xc0, 0x10, 0x20, 0x08, 0x44, 0x02, 0x80, 0x00, 0x00, // 203
	0x9a, 0x00, 0x06, 0x18, 0x62, 0x80, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, // 204
	0x8b, 0x06, 0x38, 0xc0, 0x00, 0x30, 0x00, 0x46, 0x38, 0xc0, 0x00, 0x00, // 205
	0x8b, 0x06, 0x38, 0xc0, 0x20, 0x10, 0x08, 0x06, 0x38, 0xc0, 0x00, 0x00, // 206
	0x8b, 0x0c, 0x32, 0x40, 0x82, 0x00, 0x82, 0x04, 0x98, 0x60, 0x00, 0x00, // 207
	0x8b, 0x06, 0x18, 0x60, 0x90, 0x00, 0x90, 0x00, 0x90, 0x60, 0x00, 0x00, // 208
	0x8b, 0x1c, 0x20, 0x42, 0x00, 0x8a, 0x00, 0x84, 0x0a, 0x70, 0x00, 0x00, // 209
	0x8b, 0x06, 0x18, 0x60, 0x80, 0x10, 0x88, 0x14, 0x82, 0x60, 0x00, 0x00, // 210
	0x8b, 0x04, 0x62, 0x00, 0x92, 0x00, 0x92, 0x00, 0x8c, 0x40, 0x00, 0x00, // 211
	0x8b, 0x80, 0x00, 0x86, 0x18, 0xe0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, // 212
	0x8b, 0x0c, 0x32, 0xc0, 0x02, 0x00, 0x02, 0x0c, 0x30, 0xc0, 0x00, 0x00, // 213
	0x9b, 0x00, 0xfe, 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x00, // 214
	0x8b, 0x06, 0x38, 0xc4, 0x08, 0x10, 0x08, 0x06, 0x38, 0xc0, 0x00, 0x00, // 215
	0x8b, 0x02, 0x84, 0x48, 0x20, 0x18, 0x24, 0x02, 0x40, 0x80, 0x00, 0x00, // 216
	0x8b, 0x80, 0x40, 0x26, 0x18, 0x00, 0x20, 0x00, 0x40, 0x80, 0x00, 0x00, // 217
	0x8b, 0x02, 0x04, 0x8a, 0x00, 0x92, 0x00, 0xa2, 0x40, 0x80, 0x00, 0x00, // 218
	0x9b, 0x00, 0x06, 0x18, 0x62, 0x80, 0x02, 0x80, 0x00, 0x80, 0x00, 0x00, // 219
	0xa8, 0x00, 0x00, 0xc0, 0x30, 0x0c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // 220
	0x8a, 0x02, 0x00, 0x02, 0x80, 0x06, 0x98, 0x60, 0x80, 0x00, 0x00, 0x00, // 221
	0x9a, 0x00, 0x20, 0x00, 0x40, 0x00, 0x80, 0x40, 0x20, 0x00, 0x00, 0x00, // 222
	0x0b, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, // 223
	0xb7, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 224
	0x8a, 0x04, 0x0a, 0x20, 0x0a, 0x20, 0x0a, 0x24, 0x1a, 0x00, 0x00, 0x00, // 225
	0x8a, 0x06, 0x18, 0xe2, 0x00, 0x22, 0x00, 0x24, 0x18, 0x00, 0x00, 0x00, // 226
	0x8a, 0x0c, 0x10, 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x00, 0x00, 0x00, // 227
	0x8b, 0x0c, 0x10, 0x02, 0x20, 0x02, 0x20, 0x06, 0x38, 0xc0, 0x00, 0x00, // 228
	0x8a, 0x0c, 0x12, 0x28, 0x02, 0x28, 0x02, 0x28, 0x10, 0x00, 0x00, 0x00, // 229
	0x8b, 0x20, 0x00, 0x26, 0x18, 0x60, 0x00, 0xa0, 0x00, 0x80, 0x00, 0x00, // 230
	0x1b, 0x00, 0x18, 0x25, 0x00, 0x45, 0x00, 0x46, 0x18, 0x60, 0x00, 0x00, // 231
	0x8a, 0x06, 0x18, 0xe0, 0x00, 0x20, 0x00, 0x26, 0x18, 0x00, 0x00, 0x00, // 232
	0x99, 0x00, 0x02, 0x00, 0x26, 0x18, 0x22, 0x80, 0x00, 0x00, 0x00, 0x00, // 233
	0x89, 0x01, 0x00, 0x01, 0x00, 0x26, 0x18, 0xa0, 0x00, 0x00, 0x00, 0x00, // 234
	0x8a, 0x06, 0x18, 0x60, 0x88, 0x04, 0x12, 0x00, 0x20, 0x00, 0x00, 0x00, // 235
	0x99, 0x00, 0x02, 0x00, 0x06, 0x98, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, // 236
	0x8a, 0x26, 0x18, 0x20, 0x06, 0x38, 0x00, 0x26, 0x18, 0x00, 0x00, 0x00, // 237
	0x89, 0x26, 0x18, 0x20, 0x00, 0x20, 0x06, 0x18, 0x00, 0x00, 0x00, 0x00, // 238
	0x8a, 0x0c, 0x12, 0x00, 0x20, 0x02, 0x00, 0x24, 0x18, 0x00, 0x00, 0x00, // 239
	0x0a, 0x03, 0x1c, 0x60, 0x04, 0x40, 0x04, 0x48, 0x30, 0x00, 0x00, 0x00, // 240
	0x1b, 0x00, 0x18, 0x24, 0x00, 0x44, 0x00, 0x47, 0x18, 0x60, 0x00, 0x00, // 241
	0x89, 0x06, 0x38, 0x00, 0x10, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, // 242
	0x8a, 0x02, 0x10, 0x02, 0x28, 0x02, 0x28, 0x04, 0x20, 0x00, 0x00, 0x00, // 243
	0x9a, 0x00, 0x20, 0x0c, 0x32, 0xc0, 0x22, 0x00, 0x20, 0x00, 0x00, 0x00, // 244
	0x8a, 0x0c, 0x32, 0x00, 0x02, 0x00, 0x02, 0x0c, 0x32, 0x00, 0x00, 0x00, // 245
	0x9a, 0x00, 0x3e, 0x00, 0x04, 0x00, 0x08, 0x10, 0x20, 0x00, 0x00, 0x00, // 246
	0x8b, 0x0e, 0x30, 0x04, 0x00, 0x18, 0x04, 0x00, 0x06, 0x38, 0x00, 0x00, // 247
	0x8b, 0x02, 0x00, 0x24, 0x10, 0x08, 0x04, 0x12, 0x00, 0x20, 0x00, 0x00, // 248
	0x1b, 0x00, 0x40, 0x21, 0x12, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, // 249
	0x8b, 0x02, 0x00, 0x26, 0x00, 0x2a, 0x00, 0x32, 0x00, 0x20, 0x00, 0x00, // 250
	0x9a, 0x00, 0x10, 0x04, 0x1a, 0x60, 0x82, 0x00, 0x80, 0x00, 0x00, 0x00, // 251
	0x99, 0x00, 0x02, 0x04, 0x08, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, // 252
	0x9a, 0x00, 0x02, 0x00, 0x82, 0x0c, 0xb0, 0x40, 0x10, 0x00, 0x00, 0x00, // 253
	0x8b, 0x40, 0x80, 0x00, 0x80, 0x40, 0x20, 0x00, 0x20, 0x40, 0x00, 0x00, // 254
	0x8b, 0x1a, 0x24, 0x42, 0x08, 0x92, 0x20, 0x84, 0x48, 0xb0, 0x00, 0x00  // 255
};

ImagePrinterEpson::ImagePrinterEpson(MSXMotherBoard& motherBoard_)
	: ImagePrinter(motherBoard_, false)
{
	resetEmulatedPrinter();
}

const string& ImagePrinterEpson::getName() const
{
	static const string name("epson-printer");
	return name;
}

std::string_view ImagePrinterEpson::getDescription() const
{
	return "Emulate Epson FX80 printer, prints to image.";
}

void ImagePrinterEpson::getNumberOfDots(unsigned& dotsX, unsigned& dotsY)
{
	dotsX = 610;
	dotsY = 825;
}

void ImagePrinterEpson::resetSettings()
{
	lineFeed     = 12.0;
	leftBorder   = 48;
	rightBorder  = leftBorder + 480;
	graphDensity = 1.0;
	fontDensity  = 1.0;
	pageTop      = 48;
	lines        = 72;
	fontWidth    = 6;
	eightBit     = -1;

	memcpy(fontInfo.rom, EpsonFontRom, sizeof(EpsonFontRom));
	fontInfo.charWidth = 12;
	fontInfo.pixelDelta = 0.5;
	fontInfo.useRam = false;
}

unsigned ImagePrinterEpson::calcEscSequenceLength(byte character)
{
	switch (character & 127) {
		case '!': case '-': case '/': case '3': case 'A': case 'J':
		case 'N': case 'Q': case 'R': case 'S': case 'U': case 'W':
		case 'b': case 'i': case 'j': case 'l': case 'p': case 's':
			return 1;
		case '%': case '?': case 'K': case 'L': case 'Z': case '^':
			return 2;
		case '*': case ':': case '&':
			return 3;
		case 'B': // Set tabs, variable length (up to 16 tabs)
			return 0;
		case 'C': // Set form length, variable length (2 or 3)
			return 0;
		case 'D': // Set tabs, variable length (up to 32 tabs)
			return 0;
		default:
			return 0;
	}
}

unsigned ImagePrinterEpson::parseNumber(unsigned sizeStart, unsigned sizeChars)
{
	unsigned Value = 0;
	sizeStart += sizeChars;
	while (sizeChars--) {
		Value = Value * 256 + abEscSeq[--sizeStart];
	}
	return Value;
}

void ImagePrinterEpson::processEscSequence()
{
	byte character = abEscSeq[0] & 127;

	switch (character) {
		case '!': { // Master Print Mode Select
			unsigned masterSelect  = parseNumber(1, 1);
			elite        = (masterSelect & 1) != 0;
			compressed   = (masterSelect & 4) != 0;
			bold         = (masterSelect & 8) != 0;
			doubleStrike = (masterSelect & 16) != 0;
			doubleWidth  = (masterSelect & 32) != 0;

			if (elite) {
				fontDensity = 1.20;
			} else if (compressed) {
				fontDensity = 1.72;
			} else {
				fontDensity = 1.00;
			}
			break;
		}
		case '#': // Accept Eight Bit as-is
			break;
		case '%': // Activates Character Set
			fontInfo.useRam = parseNumber(1, 1) & 1;
			break;
		case '&': // Custom character set, variable length
			ramLoadOffset = 12 * parseNumber(2, 1);
			ramLoadEnd    = 12 * parseNumber(3, 1) + 12;
			if (ramLoadEnd <= ramLoadOffset) {
				ramLoadEnd = ramLoadOffset;
			}
			break;
		case '*': // Turn Graphics Mode ON
			ninePinGraphics = false;
			switch (parseNumber(1, 1)) {
				default:
				case 0:
					graphDensity = 1.0;
					break;
				case 1: case 2:
					graphDensity = 2.0;
					break;
				case 3:
					graphDensity = 4.0;
					break;
				case 4:
					graphDensity = 1.33;
					break;
				case 5:
					graphDensity = 1.2;
					break;
				case 6:
					graphDensity = 1.5;
					break;
			}
			sizeRemainingDataBytes = parseNumber(2, 2);
			break;
		case '-': // Turn Underline Mode ON/OFF
			underline = parseNumber(1, 1) != 0;
			break;
		case '/': // Selects Vertical Tab Channel
			break;
		case '0': // Sets Line Spacing to 1/8 inch
			lineFeed = 9.0;
			break;
		case '1': // Sets Line Spacing to 7/72 inch
			lineFeed = 7.0;
			break;
		case '2': // Sets Line Spacing to 1/6 inch
			lineFeed = 12.0;
			break;
		case '3': // Sets Line Spacing to n/216 inch
			lineFeed = (parseNumber(1, 1) & 127) / 3.0;
			break;
		case '4': // Turn Italic Mode ON
			italic = true;
			break;
		case '5': // Turn Italic Mode OFF
			italic = false;
			break;
		case '6': // Turn Printing of International Italic characters ON
			noHighEscapeCodes = true;
			break;
		case '7': // Turn Printing of International Italic characters OFF
			noHighEscapeCodes = false;
			break;
		case '8': // Turn Paper Out Sensor ON
			detectPaperOut = true;
			break;
		case '9': // Turn Paper Out Sensor OFF
			detectPaperOut = false;
			break;
		case ':': // Copies Rom Character set to RAM
			memcpy(fontInfo.ram, fontInfo.rom, sizeof(fontInfo.ram));
			break;
		case '<': // Turn Uni-directional printing ON (left to right)
			leftToRight = true;
			break;
		case '=': // Sets eight bit to 0
			eightBit = 0;
			break;
		case '>': // Sets eight bit to 1
			eightBit = 1;
			break;
		case '?': // Redefines Graphics Codes
			break;
		case '@': // Reset
			eightBit = -1;
			ninePinGraphics = false;
			graphDensity = 1.0;
			fontDensity = 1.0;
			underline = false;
			lineFeed = 12.0;
			italic = false;
			detectPaperOut = false;
			leftToRight = false;
			doubleStrike = false;
			elite = false;
			compressed = false;
			rightBorder = 6 * 78;
			subscript = false;
			superscript = false;
			doubleWidth = false;
			bold = false;
			proportional = false;
			fontInfo.useRam = false;
			noHighEscapeCodes = false;
			alternateChar = false;
			countryCode = CC_USA;
			break;
		case 'A': // Sets Line Spacing to n/72 inch
			lineFeed = parseNumber(1, 1) & 127;
			break;
		case 'B': // Set tabs, variable length (up to 16 tabs)
			break;
		case 'C': // Set form length, variable length (2 or 3)
			break;
		case 'D': // Set tabs, variable length (up to 32 tabs)
			break;
		case 'E': // Turn Emphasized Mode ON
			bold = true;
			break;
		case 'F': // Turn Emphasized Mode OFF
			bold = false;
			break;
		case 'G': // Turn Double Strike Mode ON
			doubleStrike = true;
			break;
		case 'H': // Turn Double Strike Mode OFF
			doubleStrike = false;
			break;
		case 'I': // Enables printing of chars 1-31
			alternateChar = parseNumber(1, 1) & 1;
			break;
		case 'J': // Forces Line Feed with n/216 inch
			vpos += (parseNumber(1, 1) & 127) / 3.0;
			if (vpos >= pageHeight) {
				flushEmulatedPrinter();
			}
			break;
		case 'K': // Turn Single Density Graphics on (480 dot mode)
			graphDensity = 1.0;
			ninePinGraphics = false;
			sizeRemainingDataBytes = parseNumber(1, 2);
			break;
		case 'L': // Turn Double Density Graphics on (960 dot mode)
			graphDensity = 2.0;
			ninePinGraphics = false;
			sizeRemainingDataBytes = parseNumber(1, 2);
			break;
		case 'M': // Turn Elite mode ON
			elite = true;
			fontDensity = 1.20;
			break;
		case 'N': // Turn Skip Over Perforation ON
			break;
		case 'O': // Turn Skip Over Perforation OFF
			break;
		case 'P': // Turn Elite mode OFF
			elite = false;
			fontDensity = compressed ? 1.72 : 1.00;
			break;
		case 'Q': { // Set Right Margin
			int width = parseNumber(1, 2);
			if (width > 78) width = 78; // FIXME Font dependent !!
			rightBorder = 6 * width;
			break;
		}
		case 'R': // Select International Character Set
			countryCode = static_cast<CountryCode>(parseNumber(1, 1));
			if (countryCode > CC_JAPAN) {
				countryCode = CC_USA;
			}
			break;
		case 'S': { // Turn Script Mode ON
			int script = parseNumber(1, 1) & 1;
			superscript = script == 0;
			subscript   = script == 1;
			break;
		}
		case 'T': // Turn Script Mode OFF
			subscript = false;
			superscript = false;
			break;
		case 'U': // Turn Uni-directional mode ON/OFF
			leftToRight = parseNumber(1, 1) != 0;
			break;
		case 'W': // Turn Expanded Mode ON/OFF
			normalAfterLine = false;
			doubleWidth = parseNumber(1, 1) != 0;
			break;
		case 'Y': // Turn High Speed Double Density Graphics ON
			break;
		case 'Z': // Turns Quadruple Density Graphics ON
			graphDensity = 4.0;
			ninePinGraphics = false;
			sizeRemainingDataBytes = parseNumber(1, 2);
			break;
		case '^': // Turn Nine Pin Graphics Mode ON
			graphDensity = parseNumber(1, 1) ? 2.0 : 1.0;
			ninePinGraphics = true;
			sizeRemainingDataBytes = 2 * parseNumber(2, 2);
			break;
		case 'b': // Set Vertical Tab
			break;
		case 'i': // Turn Immediate Mode ON/OFF
			break;
		case 'j': // Immediate Reverse Line Feed
			vpos -= (parseNumber(1, 1) & 127) / 3.0;
			if (vpos < pageTop) {
				vpos = pageTop;
			}
			break;
		case 'l': // Set Left Margin
			break;
		case 'p': // Turn proportional mode ON/OFF
			proportional = parseNumber(1, 1) != 0;
			break;
		case 's': // Set Print Speed
			break;
		case 127: // Deletes Last Character in Buffer
			break;
	}
}

// International character code translation for the Epson FX-80 printer
//                              US   FR   DE   GB   DK   SE   IT   SP   JP
static byte intlChar35 [9] = {  35,  35,  35,   6,  35,  35,  35,  12,  35 };
static byte intlChar36 [9] = {  36,  36,  36,  36,  36,  11,  36,  36,  36 };
static byte intlChar64 [9] = {  64,   0,  16,  64,  64,  29,  64,  64,  64 };
static byte intlChar91 [9] = {  91,   5,  23,  91,  18,  23,   5,   7,  91 };
static byte intlChar92 [9] = {  92,  15,  24,  92,  20,  24,  92,   9,  31 };
static byte intlChar93 [9] = {  93,  16,  25,  93,  13,  13,  30,   8,  93 };
static byte intlChar94 [9] = {  94,  94,  94,  94,  94,  25,  94,  94,  94 };
static byte intlChar96 [9] = {  96,  96,  96,  96,  96,  30,   2,  96,  96 };
static byte intlChar123[9] = { 123,  30,  26, 123,  19,  26,   0,  22, 123 };
static byte intlChar124[9] = { 124,   2,  27, 124,  21,  27,   3,  10, 124 };
static byte intlChar125[9] = { 125,   1,  28, 125,  14,  14,   1, 125, 125 };
static byte intlChar126[9] = { 126,  22,  17, 126, 126,  28,   4, 126, 126 };

void ImagePrinterEpson::processCharacter(byte data)
{
	if (data >= 32) {
		if (italic) {
			data |= 128;
		} else {
			data &= 127;
		}
	}

	if (!noHighEscapeCodes && data >= 128 && data < 160) {
		data &= 31;
	}

	// Convert international characters
	switch (data & 0x7f) {
		case 35:  data = (data & 0x80) | intlChar35 [countryCode]; break;
		case 36:  data = (data & 0x80) | intlChar36 [countryCode]; break;
		case 64:  data = (data & 0x80) | intlChar64 [countryCode]; break;
		case 91:  data = (data & 0x80) | intlChar91 [countryCode]; break;
		case 92:  data = (data & 0x80) | intlChar92 [countryCode]; break;
		case 93:  data = (data & 0x80) | intlChar93 [countryCode]; break;
		case 94:  data = (data & 0x80) | intlChar94 [countryCode]; break;
		case 96:  data = (data & 0x80) | intlChar96 [countryCode]; break;
		case 123: data = (data & 0x80) | intlChar123[countryCode]; break;
		case 124: data = (data & 0x80) | intlChar124[countryCode]; break;
		case 125: data = (data & 0x80) | intlChar125[countryCode]; break;
		case 126: data = (data & 0x80) | intlChar126[countryCode]; break;
	}

	if (data >= 32) {
		printVisibleCharacter(data);
		return;
	}

	switch (data) {
		case 0: // Terminates horizontal and vertical TAB setting
			break;
		case 7: // Sound beeper
			break;
		case 8: // Backspace
			// TODO: fix for other font-sizes
			hpos -= 8;
			if (hpos < leftBorder) {
				hpos = leftBorder;
			}
			break;
		case 9: // Horizontal TAB
			// TODO: fix for other font-sizes
			hpos = ((unsigned(hpos) + 64 - leftBorder) & ~63)
			     + leftBorder;
			if (hpos < rightBorder) {
				break;
			}
			hpos = leftBorder;
			[[fallthrough]];
		case 10: // Line Feed
		case 11: // Vertical TAB
			vpos += lineFeed;
			if (vpos >= pageHeight) {
				flushEmulatedPrinter();
			}
			break;
		case 12: // Form Feed
			ensurePrintPage();
			flushEmulatedPrinter();
			break;
		case 13: // Carrige return
			hpos = leftBorder;
			break;
		case 14: // Turns expanded mode ON
			doubleWidth = true;
			normalAfterLine = true;
			break;
		case 15: // Shift in. Emties buffer, turns compressed mode ON (17.16 cpi)
			compressed = true;
			if (!elite) {
				fontDensity = 1.72;
			}
			break;
		case 17: // Device Control 1:
			break;
		case 18: // Device Control 2: turns compressed mode OFF
			compressed = false;
			fontDensity = 1.00;
			break;
		case 19: // Device Control 3:
			break;
		case 20: // Device Control 4: Turns expanded mode OFF
			doubleWidth = false;
			break;
		case 24: // Cancels all text in the print buffer
			break;
		case 27: // Escape
			escSequence = true;
			break;
		default:
			if (alternateChar) {
				printVisibleCharacter(data);
			}
			break;
	}
}

template<typename Archive>
void ImagePrinterEpson::serialize(Archive& /*ar*/, unsigned /*version*/)
{
	// TODO is this worth it?
}
INSTANTIATE_SERIALIZE_METHODS(ImagePrinterEpson);
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, ImagePrinterEpson, "ImagePrinterEpson");


// class Paper

Paper::Paper(unsigned x, unsigned y, double dotSizeX, double dotSizeY)
	: buf(x * y)
	, sizeX(x), sizeY(y)
{
	memset(buf.data(), 255, x * y);
	setDotSize(dotSizeX, dotSizeY);
}

string Paper::save() const
{
	string filename = FileOperations::getNextNumberedFileName(
		"prints", "page", ".png");
	VLA(const void*, rowPointers, sizeY);
	for (unsigned y = 0; y < sizeY; ++y) {
		rowPointers[y] = &buf[sizeX * y];
	}
	PNG::saveGrayscale(sizeX, sizeY, rowPointers, filename);
	return filename;
}

void Paper::setDotSize(double dotSizeX, double dotSizeY)
{
	radiusX = dotSizeX / 2.0;
	radiusY = dotSizeY / 2.0;

	int rx = int(16 * radiusX);
	int ry = int(16 * radiusY);
	radius16 = ry;

	table.clear();
	table.resize(2 * (radius16 + 16), -(1 << 30));

	int offset = ry + 16;
	int rx2 = 2 * rx * rx;
	int ry2 = 2 * ry * ry;

	int x = 0;
	int y = ry;
	int de_x = ry * ry;
	int de_y = (1 - 2 * ry) * rx * rx;
	int e = 0;
	int sx = 0;
	int sy = rx2 * ry;
	while (sx <= sy) {
		table[offset - y - 1] = x;
		table[offset + y    ] = x;
		x    += 1;
		sx   += ry2;
		e    += de_x;
		de_x += ry2;
		if ((2 * e + de_y) > 0) {
			y    -= 1;
			sy   -= rx2;
			e    += de_y;
			de_y += rx2;
		}
	}

	x = rx;
	y = 0;
	de_x = (1 - 2 * rx) * ry * ry;
	de_y = rx * rx;
	e = 0;
	sx = ry2 * rx;
	sy = 0;
	while (sy <= sx) {
		table[offset - y - 1] = x;
		table[offset + y    ] = x;
		y    += 1;
		sy   += rx2;
		e    += de_y;
		de_y += rx2;
		if ((2 * e + de_x) > 0) {
			x -= 1;
			sx -= ry2;
			e += de_x;
			de_x += ry2;
		}
	}
}

void Paper::plot(double xPos, double yPos)
{
	unsigned xx1 = max<int>(int(floor(xPos - radiusX)), 0);
	unsigned xx2 = min<int>(int(ceil (xPos + radiusX)), sizeX);
	unsigned yy1 = max<int>(int(floor(yPos - radiusY)), 0);
	unsigned yy2 = min<int>(int(ceil (yPos + radiusY)), sizeY);

	int y = 16 * yy1 - int(16 * yPos) + 16 + radius16;
	for (unsigned yy = yy1; yy < yy2; ++yy) {
		int x = 16 * xx1 - int(16 * xPos);
		for (unsigned xx = xx1; xx < xx2; ++xx) {
			int sum = 0;
			for (int i = 0; i < 16; ++i) {
				int a = table[y + i];
				if (x < -a) {
					int t = 16 + a + x;
					if (t > 0) {
						sum += min(t, 2 * a);
					}
				} else {
					int t = a - x;
					if (t > 0) {
						sum += min(16, t);
					}
				}
			}
			dot(xx, yy) = max(0, dot(xx, yy) - sum);
			x += 16;
		}
		y += 16;
	}
}

byte& Paper::dot(unsigned x, unsigned y)
{
	assert(x < sizeX);
	assert(y < sizeY);
	return buf[y * sizeX + x];
}

} // namespace openmsx
